#include "Ogre.h"

using namespace Ogre;

static const float R_g = 6360.0f;
static const float R_t = 6430.0f;
static const float dist_step = 1.0f;	// 1km
static const float H_rayleigh = 8.4f;	// 8.4km
static const float H_mie = 1.2f;		// 1.2km

Vector2 CalcOpticalDepth(float CosZenithAngle, float altitude)
{
	float SinZenithAngle = sqrtf(1 - CosZenithAngle*CosZenithAngle);
	float dist = 0;
	float start_height = R_g + altitude;
	float height = start_height;
	Vector2 Start(0, start_height), End;
	Vector2 optical_depth(0, 0);
	while (height < R_t)
	{
		dist += dist_step;
		End.x = dist * SinZenithAngle;
		End.y = start_height + dist * CosZenithAngle;
		height = End.length();

		if (height < R_g)
		{
			// if the ray hits the ground, then exit with a big number.
			return Vector2(1e6f, 1e6f);
		}

		float start_alt = Start.length() - R_g;
		float end_alt = height - R_g;
#ifdef OPTICAL_DEPTH_AVERAGE_ALTITUDE
		// average altitude
		float average_alt = (start_alt + end_alt) * 0.5f;
		optical_depth.x += dist_step * 1000.0f * expf(-average_alt / H_rayleigh);
		optical_depth.y += dist_step * 1000.0f * expf(-average_alt / H_mie);
#else
		// average density
		optical_depth.x += dist_step * 1000.0f * 0.5f * (expf(-start_alt / H_rayleigh) + expf(-end_alt / H_rayleigh));
		optical_depth.y += dist_step * 1000.0f * 0.5f * (expf(-start_alt / H_mie) + expf(-end_alt / H_mie));
#endif
		Start = End;
	}
	return optical_depth;
}

void createOpticalDepthTexture(int width, int height)
{
	TexturePtr texture = TextureManager::getSingleton().createManual(
		"OpticalDepthTexture",
		ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
		TEX_TYPE_2D,
		width, height,
		0,                // number of mipmaps
		PF_FLOAT32_GR,     // pixel format
		TU_DEFAULT);      // usage

	HardwarePixelBufferSharedPtr pixelBuffer = texture->getBuffer();
	pixelBuffer->lock(HardwareBuffer::HBL_DISCARD);
	const PixelBox& pixelBox = pixelBuffer->getCurrentLock();
	float* pDest = static_cast<float*>(pixelBox.data);
	for (int y = 0; y < height; y++)
	{
		float altitude = (R_t - R_g) * y / (height - 1);
		for (int x = 0; x < width; x++)
		{
			float CosZenithAngle = 2.0f * x / (width - 1) - 1.0f;
			Vector2 od = CalcOpticalDepth(CosZenithAngle, altitude);
			*pDest++ = od.x;
			*pDest++ = od.y;
		}
		pDest += pixelBox.getRowSkip() * Ogre::PixelUtil::getNumElemBytes(pixelBox.format);
	}
	pixelBuffer->unlock();
}